Add a C module for handling buttons.#295
Conversation
|
In addition to adding the description from the issue here is more feedback. What do you think about renaming this to I also think it should be a proper object rather than using statically allocated memory. That gives more flexibility on its use going forwards without impacting the size of a global state. The only global state should be the object mechanics (functions and name dictionaries) plus a global for a pointer to a Gamepad object that tick updates. Please add this to the top-level shared-bindings too. I think its something folks should add to other ports as needed. Document its availability here: https://github.com/adafruit/circuitpython/blob/master/shared-bindings/index.rst Thanks! Your description on the issue made this click for me and now I'm excited about it. |
Let me see if I understand this correctly. I would have a global (extern) pointer to a No, sorry, it doesn't make sense, C doesn't have methods, so |
|
I think we're close to the same page. Although C doesn't have methods, its common for CircuitPython code to pass around a struct representing an object as the first argument similar to how Python passes self around. |
|
Ah, so instead of |
|
Yeah, you could either throw an exception or have the underlying structs be a linked list that can be updated. |
|
Then we also need a way to remove buttons, and to deinitialize the whole thing, and it becomes much more complicated. |
|
I will try to use an object internally, but leave the API as it is for now, we can test it and think about a better API then. |
|
Why do you need a way to remove buttons? Needing to deinit is a common problem in the existing APIs. They do it through |
|
It seems natural that if you can enable something, you should also be able to disable it. One use case that comes to mind is a common trick with sharing buttons with the SPI pins — the buttons are connected through resistors, so that when the CS pin is asserted, the pins are in output mode and SPI has control over the lines, but when we finish and deassert CS, we switch them back into input and read the button states from them. It's a bit of a hack, but surprisingly common, and it would be useful on boards like Trinket M0, where the SPI uses up almost all pins, but you probably would still want to have some buttons with that screen. |
|
I tried it with a dynamically allocated object, but the extra code made it not fit on the Trinket anymore. Is there an important reason to avoid those 8 statically allocated bytes, or is this just a question of style? |
|
Could you push that version anyway? Its mostly style because static storage is always used vs the heap which is dynamic. |
|
I will try again, but I really wonder if it's worth it to fight for those 8 bytes, especially sice the global pointer to the object alone will take 4 bytes already, not even counting all the extra code that is needed. (I just realized that it's an array of pointers, not of pin numbers, so it's actually 32 bytes.) |
|
I re-did the |
tannewt
left a comment
There was a problem hiding this comment.
Thanks for a rev on this! I realize I'm being picky but I want to make sure core APIs are consistent before adding them.
You've convinced me this is useful so I'd love it if you'd see it through.
Regarding passing it around as state, that would make things simpler for small things but global state can make things complicated. I'd like to draw the boundary between state that needs to kept around and global state by acknowledging what state persists across VM runs and what doesn't. Files and autoreload are examples of state that persists across VMs while this and all of the other hardware interactions get reset after a VM ends.
I hope that makes sense. I'd love to hear about other changes you are making for pew pew because we could have it in this repo as an experimental build.
| //| :platform: SAMD21 | ||
| //| | ||
|
|
||
| //| ..method:: GamePad.get_pressed() |
There was a problem hiding this comment.
Move this below the class, then indent it and it'll be included in the class automatically. Here is an example: https://github.com/adafruit/circuitpython/blob/master/shared-bindings/touchio/TouchIn.c#L54
I also think pressed should be a property rather than a method because its state.
There was a problem hiding this comment.
I also think pressed should be a property rather than a method because its state.
I would make it a property, if not for the side effect, which is clearing the current state. The way this works, it accumulates button presses, until you call "get_pressed", which gives you what has been pressed since the last call, and clears the buffer. Making it a property would lead to surprising, unexpected behaviors where the status is cleared more than once, etc.
I made the status clearing functionality more explicit in the description.
There was a problem hiding this comment.
Also note, that it is important that the status is retrieved and cleared in one atomic operation, and not first retrieved, and then cleared separately — the latter risks missing some button presses.
There was a problem hiding this comment.
Ok! Looks good.
Another critical point in my mind is that it includes past presses. The state module works better when there are no side effects and it only is actively pressed buttons. Thanks for the push back!
| .name = MP_QSTR_GamePad, | ||
| .make_new = gamepad_make_new, | ||
| .locals_dict = (mp_obj_dict_t*)&gamepad_locals_dict, | ||
| }; |
There was a problem hiding this comment.
Please move the GamePad object and header to a separate file to match TouchIn and others. https://github.com/adafruit/circuitpython/blob/master/shared-bindings/touchio/TouchIn.c#L54
| //| immediately get switched to input with a pull-up, and then scanned | ||
| //| regularly for button presses. The order is the same as the order of | ||
| //| bits returned by the ``get_pressed`` function. To disable button | ||
| //| scanning, call this without any arguments. |
There was a problem hiding this comment.
Disabling this by calling it again with no arguments seems weird to me. Could you add deinit to match other classes instead? Thanks!
|
Thank you for your guidance, I'm still not very well oriented in how it all is organized, your comments are certainly most helpful. I'm still not sure what to do with the conflicts. I originally wrote this for the 2.x version, and it seems that in 3.x there has been some changes — to which version do we want to merge this? |
|
|
||
| //| ..class:: GamePad([b1[, b2[, b3[, b4[, b5[, b6[, b7[, b8]]]]]]]]) | ||
| //| | ||
| //| Initializes button scanning routines. |
There was a problem hiding this comment.
Have you tested this with sphinx or readthedocs? I think this chunk and below needs to be indented.
(RST was created by python people I think so its white space sensitive too.)
There was a problem hiding this comment.
I haven't, I will fix it and test, sorry.
| //| .. module:: gamepad | ||
| //| :synopsis: Button handling | ||
| //| :platform: SAMD21 | ||
| //| |
There was a problem hiding this comment.
35-41 can go in init.c and do .. currentmodule:: gamepad here.
Example touchio docs: https://github.com/adafruit/circuitpython/blob/master/shared-bindings/touchio/__init__.c
| #define CIRCUITPY_AUTORELOAD_DELAY_MS 500 | ||
| #define CIRCUITPY_BOOT_OUTPUT_FILE "/boot_out.txt" | ||
| // Scan gamepad every 32ms | ||
| #define CIRCUITPY_GAMEPAD_TICKS 0x1f |
There was a problem hiding this comment.
This should be in the express board portion right? Currently this will call ticks but the singleton will always be null.
There was a problem hiding this comment.
Not sure what you mean. This defines how often the buttons are scanned, and also lets you disable the scanning completely. I put it here, because it's configuration. The gamepad_singleton is not related to it, but it won't be always NULL — it's being assigned to in the gamead_make_new function.
There was a problem hiding this comment.
Right, the scanning should be disabled on non-express builds. With this here, non-express boards will still include https://github.com/adafruit/circuitpython/pull/295/files#diff-ae7239759d3115ddffbb5b5fd105f23aR21 and gamepad_ticks().
| //| :platform: SAMD21 | ||
| //| | ||
|
|
||
| //| ..method:: GamePad.get_pressed() |
There was a problem hiding this comment.
Ok! Looks good.
Another critical point in my mind is that it includes past presses. The state module works better when there are no side effects and it only is actively pressed buttons. Thanks for the push back!
tannewt
left a comment
There was a problem hiding this comment.
A few minor doc things.
Pulling this into 2.x as you've tested is fine. I want to move people to master quickly but I've just done it a bit prematurely. I'll make sure everything in 2.x makes it into master.
|
That said, you should be able to test this in master with the REPL if you want to try and merge to master. The filesystem stuff isn't working yet though. |
|
Thank you for the review, I incorporated your suggestions, and also added a short usage example. |
tannewt
left a comment
There was a problem hiding this comment.
Great! Which branch do you want to merge it into?
|
I tested it with 2.x, so I think it would make sense to merge it there. Should I make a second pull request for that branch, or can you choose it while merging? |
Fix #279
This adds a
buttonsmodule to the samd port, with two functions useful for button handling:setup()andget_pressed(). The buttons are automatically scanned every 32 ticks, de-bounced, and reported to the user as a bit mask.